﻿using Forum.Model.Entity;
using Forum.Model.Request;
using Forum.Repository.Interface;
using Forum.Infrastructure;
using Forum.Infrastructure.Util;
using Forum.Model;
using Microsoft.Extensions.Options;
using Forum.Model.Response;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Forum.Service.Interface.NoAuth;
using Forum.Model.Entity.System;

namespace Forum.Service.Service.NoAuth
{
    public class AuthService : IAuthService
    {
        private readonly IAppRepository<User> Repository;
        private readonly IAppRepository<RefreshToken> _TokenRepository;
        private readonly JWTTokenOptions _JWTTokenOptions;

        public AuthService(IAppRepository<User> appRepository, IAppRepository<RefreshToken> TokenRepository, IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
        {
            Repository = appRepository;
            _TokenRepository = TokenRepository;
            _JWTTokenOptions = jwtTokenOptions.CurrentValue;
        }

        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public async Task<UserLoginInfo?> LoginAsync(AuthReq user)
        {
            var userInfo = Repository.FindEntity(a => (a.UserName == user.username || a.NickName == user.username || a.UserCode == user.username) && a.Password == user.password);
            if (userInfo != null)
            {
                var userLoginInfo = new UserLoginInfo()
                {
                    user = userInfo,
                    token = await GenerateJwtToken(userInfo)
                };
                return userLoginInfo;
            }
            return null;
        }

        /// <summary>
        /// 获取Token
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public TokenResponse? Token(AuthReq user)
        {
            //验证用户信息
            var userInfo = Repository.FindEntity(a => (a.UserName == user.username || a.NickName == user.username || a.UserCode == user.username) && a.Password == user.password);
            if (userInfo != null)
            {
                var expiration = DateTime.Now.AddHours(2);
                TokenResponse resp = new TokenResponse();
                resp.token = JwtTokenUtil.CreateToken(JsonConvert.SerializeObject(userInfo), _JWTTokenOptions.SecurityKey, _JWTTokenOptions.Audience, expiration);
                resp.expiration = expiration;

                var refresh_expiration = DateTime.Now.AddHours(24);
                resp.refresh_token = JwtTokenUtil.CreateToken(_JWTTokenOptions.SecurityKey, _JWTTokenOptions.SecurityKey, _JWTTokenOptions.Audience, expiration);
                resp.refresh_exp = refresh_expiration;
                return resp;
            }
            return null;
        }

        /// <summary>
        /// 获取Token
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public async Task<TokenResult> TokenAsync(AuthReq user)
        {
            var userInfo = Repository.FindEntity(a => (a.UserName == user.username || a.NickName == user.username || a.UserCode == user.username) && a.Password == user.password);

            return await GenerateJwtToken(userInfo);
        }

        /// <summary>
        /// 刷新Token
        /// </summary>
        /// <param name="token"></param>
        /// <param name="refreshToken"></param>
        /// <returns></returns>
        public async Task<TokenResult> RefreshTokenAsync(string token, string refreshToken)
        {
            var claimsPrincipal = GetClaimsPrincipalByToken(token);
            if (claimsPrincipal == null)
            {
                return new TokenResult() { Errors = "无效的Token!" };
            }

            var expiryDateUnix = long.Parse(claimsPrincipal.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value);
            var expiryDateTimeUtc = UnixTimeStampToDateTime(expiryDateUnix);
            if (expiryDateTimeUtc > DateTime.UtcNow)
            {
                return new TokenResult() { Errors = "Token未过期!" };
            }

            var jti = claimsPrincipal.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value;

            var storedRefreshToken = await _TokenRepository.FindEntityAsync(x => x.Token == refreshToken);
            if (storedRefreshToken == null)
            {
                return new TokenResult() { Errors = "无效的refresh_token!" };
            }
            if (storedRefreshToken.ExpiryTime < DateTime.UtcNow)
            {
                return new TokenResult() { Errors = "refresh_token已过期!" };
            }
            if (storedRefreshToken.Invalidated)
            {
                return new TokenResult() { Errors = "refresh_token已失效!" };
            }
            if (storedRefreshToken.Used)
            {
                return new TokenResult() { Errors = "refresh_token已使用!" };
            }
            if (storedRefreshToken.JwtId != jti)
            {
                return new TokenResult() { Errors = "refresh_token与此token不匹配!" };
            }

            storedRefreshToken.Used = true;

            await _TokenRepository.UpdateAsync(storedRefreshToken);

            var dbUser = await Repository.FindEntityAsync(storedRefreshToken.UserId.ToString());
            return await GenerateJwtToken(dbUser);
        }

        private ClaimsPrincipal? GetClaimsPrincipalByToken(string token)
        {
            try
            {
                var tokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_JWTTokenOptions.SecurityKey)),
                    ClockSkew = TimeSpan.Zero,
                    ValidateLifetime = false // 不验证过期时间！！！
                };

                var jwtTokenHandler = new JwtSecurityTokenHandler();

                var claimsPrincipal =
                    jwtTokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken);

                var validatedSecurityAlgorithm = validatedToken is JwtSecurityToken jwtSecurityToken
                                                 && jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
                                                     StringComparison.InvariantCultureIgnoreCase);

                return validatedSecurityAlgorithm ? claimsPrincipal : null;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 创建Token
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        private async Task<TokenResult> GenerateJwtToken(User user)
        {
            //创建Token
            var tup = JwtTokenUtil.CreateToken(user.UserId, _JWTTokenOptions.Audience, _JWTTokenOptions.SecurityKey, _JWTTokenOptions.ExpiresIn);

            //写入数据库
            var refreshToken = new RefreshToken()
            {
                JwtId = tup.jwtId,
                UserId = user.UserId,
                CreationTime = DateTime.Now,
                ExpiryTime = DateTime.Now.AddDays(1),
                Token = GenerateRandomNumber()
            };
            await _TokenRepository.InsertAsync(refreshToken);

            //返回数据
            return new TokenResult()
            {
                AccessToken = tup.token,
                TokenType = "Bearer",
                RefreshToken = refreshToken.Token,
                ExpiresIn = (int)_JWTTokenOptions.ExpiresIn.TotalSeconds,
                Expiration = DateTime.Now.AddSeconds(_JWTTokenOptions.ExpiresIn.TotalSeconds)
            };
        }

        /// <summary>
        /// 生成RefreshToken
        /// </summary>
        /// <param name="len"></param>
        /// <returns></returns>
        private string GenerateRandomNumber(int len = 32)
        {
            var randomNumber = new byte[len];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }

        /// <summary>
        /// 将时间戳转成UTC时间
        /// </summary>
        /// <param name="unixTimeStamp"></param>
        /// <returns></returns>
        private DateTime UnixTimeStampToDateTime(long unixTimeStamp)
        {
            DateTime dtStart = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Local);

            long lTime = unixTimeStamp * 10000000;
            TimeSpan toNow = new TimeSpan(lTime);
            DateTime targetDt = dtStart.Add(toNow);
            return targetDt;
        }
    }
}
